*******************
Command-line client
*******************

SYNOPSIS
========

:command:`sievemgr` [:option:`server`] [:option:`command`]
[:option:`argument` ...]

:command:`sievemgr` :option:`-e expression` [...] [:option:`server`]

:command:`sievemgr` :option:`-s file` [:option:`server`]

:command:`sievemgr` :option:`-h`

:command:`sievemgr` :option:`-V`


DESCRIPTION
===========

:command:`sievemgr` is a command-line client for uploading, downloading,
and managing Sieve scripts using the ManageSieve protocol. By default,
a shell is entered and commands are read from standard input;
the shell supports tab-completion. See COMMANDS_ below for a list.

Commands can also be given either on the command line, as `expression`
with :option:`-e`, or read from a `file` given with :option:`-s`.
The shell is not entered if commands are given.

.. only:: man

    The `server` defaults to the :confvar:`host`
    set in :manpage:`sieve.cf(5)` or :samp:`localhost`.

.. only:: not man

    The `server` defaults to the :confvar:`host`
    set in :doc:`sieve.cf <config>` or :samp:`localhost`.


OPERANDS
========

.. option:: server

    URL of the form
    :samp:`[sieve://][{login}[:{passwd}]@]{host}[:{port}][/{owner}]`.

    `login`
        .. only:: man

            defaults to the :confvar:`login` set for `host` in
            :manpage:`sieve.cf(5)` or :file:`.netrc`,
            or to the current user.

        .. only:: not man

            defaults to the :confvar:`login` set for `host`
            in :doc:`sieve.cf <config>` or :file:`.netrc`,
            or to the current user.

    `passwd`
        is prompted for by default (see LOGIN_ for automation).

    `port`
        defaults to 4190 (the standard port for ManageSieve).

    `owner`
        defaults to `login`.

    .. danger::

        Other users can see passwords given on the command line.

.. option:: command

    Command to run (see COMMANDS_ below).

.. option:: argument

    Argument to that command.


OPTIONS
=======

.. option:: -C

    Do *not* overwrite files.

.. option:: -N file

    Use `file` as :file:`.netrc` file.

.. option:: -V

    Print version.

.. option:: -c file

    Read configuration from `file`.

.. option:: -d

    Enable debugging mode.

.. option:: -e expression

    Execute `expression` on the `server`.

.. option:: -f

    Overwrite and remove files without confirmation.

.. option:: -h

    Print help.

.. option:: -i

    Confirm removing or overwriting files.

.. option:: -o key=value

    Set the configuration `key` to `value`.

    :samp:`-o {key}=yes` can be shortened to :samp:`-o {key}`.
    :samp:`-o {key}=no` can be shortened to :samp:`-o no{key}`.

    .. only:: man

        See :manpage:`sieve.cf(5)` for a list of keys.

    .. only:: not man

        See :doc:`sieve.cf <config>` for a list of keys.

.. option:: -q

    Be quieter.

.. option:: -s file

    Execute expressions read from `file`.

.. option:: -v

    Be more verbose.

:option:`-c`, :option:`-e`, :option:`-o`, :option:`-q`, and :option:`-v`
can be given multiple times.


COMMANDS
========

.. sievecmd:: about

    Print information about SieveManager.

.. sievecmd:: activate script

    Marks `script` as the active script.
    This is the script that the mail server will run for incoming mail.
    Only one script can be active at a time.

.. sievecmd:: caps

    Print the server's capabilities (YAML_).

.. sievecmd:: cat [script ...]

    Print the given scripts to standard output.

.. sievecmd:: cd [localdir]

    Change to local working directory to `localdir`.
    `localdir` defaults to the current user's home directory.

.. sievecmd:: cert

    Print information about the server's TLS certificate (YAML_).

.. sievecmd:: check localscript

    Check whether `localscript` is semantically valid.

.. sievecmd:: cmp script1 [...] scriptN

    Check whether scripts are equal.

.. sievecmd:: cp [-f|-i] source target

    Download `source` and re-upload it as `target`.

    .. rubric:: Options:

    -f  Overwrite `target` without confirmation.
    -i  Ask for confirmation before overwriting `target`.

.. sievecmd:: deactivate

    Deactivate the active script

.. sievecmd:: diff [-C n|-U n|-c|-u] [-b] script1 script2

    Show how `script1` and `script2` differ.

    .. rubric:: Options:

    -C n  Show `n` lines of copied context.
    -U n  Show `n` lines of unified context.
    -b    Ignore whitespace before a linefeed.
    -c    Show three lines of copied context.
    -u    Show three lines of unified context.

.. sievecmd:: echo word [...]

    Print `word` to standard output.

.. sievecmd:: ed [-a] script [...]

    Download `script`, edit it with a line editor, and re-upload it.

    .. rubric:: Options:

    -a  Edit the active script (any `script` given is ignored).

.. sievecmd:: exit

    Log out and exit.

.. sievecmd:: get [-f|-i] [-a] [-o file] [script ...]

    Download scripts.

    .. rubric:: Options:

    -a       Download the active script only.
    -f       Overwrite files without confirmation.
    -i       Ask for confirmation before overwriting a file.
    -o file  Save `script` as :file:`file`.

.. sievecmd:: help | ? [command]

    Print help for `command`.
    List commands if `command` is omitted.

    For example:

    .. code:: none

        sieve://user@imap.foo.example> ?ls
        ls [script ...] - list scripts

.. sievecmd:: ls [-1al] [script ...]

    List the given scripts or all scripts if no `script` is given.
    The active script is marked with an asterisk ("\*").

    .. rubric:: Options:

    -1  List one script per line.
        Implied if standard input is not a terminal.

    -a  List the active script only.

    -l  List one flag-script name pair per line, separated by whitespace,
        where the flag is one of:

        :literal:`a`
            Script is active.

        :literal:`e`
            End-of-transmission mark.
            *Not* followed by a script name.

        If neither flag applies, a dash ("-") is printed instead.

    The active script is *not* marked with an asterisk ("\*") if
    ``-1``, ``-a``, or ``-l`` is given.

    .. rubric:: Examples:

    .. code:: none

        sieve://user@imap.foo.example> ls
        bar.sieve foo.sieve*

    .. code:: none

        sieve://user@imap.foo.example> ls -l
        - bar.sieve
        a foo.sieve
        e

.. sievecmd:: more [-aceis] [script ...]

    Display scripts page-by-page.

    .. rubric:: Options:

    -a          Display the active script only.
    -c          Clear screen instead of scrolling.
    -e          Exit immediately after writing the last line.
    -i          Ignore case in pattern matching.
    -s          Treat consecutive empty lines as a single empty line.

.. sievecmd:: mv [-f|-i] source target

    Rename `source` to `target`.

    .. rubric:: Options:

    -f  Replace `target` without confirmation.
    -i  Ask for confirmation before replacing `target`.

.. sievecmd:: put [-f|-i] [-a] [-o name] [localscript ...]

    Upload scripts.

    -a       Replace the active script or, if no script is active
             or if ``-o`` has been given *after* ``-a``, activate
             the script after uploading.
    -f       Replace scripts without confirmation.
    -i       Ask for confirmation before replacing a script.
    -o name  Upload `localscript` as `name`.

    The server should reject syntactically invalid scripts.
    It may issue a warning for semantically invalid scripts,
    but should accept them nonetheless. Updates are atomic.

.. sievecmd:: python

    Enter a Python read-evaluate-print loop, with the
    :mod:`SieveManager <sievemgr.SieveManager>` object
    that represents the connection as namespace.

.. sievecmd:: rm [-f|-i] [script ...]

    Remove scripts.

    -f  Remove scripts without confirmation.
    -i  Ask for confirmation before removing a script.

.. sievecmd:: sh | ! [command] [argument ...]

    Run system `command`. If `command` is omitted, enter a system shell.

    .. rubric:: Examples:

    .. code:: none

        sieve://user@imap.foo.example> cd sieve
        sieve://user@imap.foo.example> !pwd
        /home/user/sieve
        sieve://user@imap.foo.example> !ls
        foo.sieve bar.sieve
        sieve://user@imap.foo.example> put foo.sieve

    .. code:: none

        sieve://user@imap.foo.example> !
        bash-2.0$

.. sievecmd:: su user

    Manage the scripts of `user`.
    Requires elevated privileges.

.. sievecmd:: vi [-a] script [...]

    Download `script`, edit it with a visual editor, and re-upload it.

    .. rubric:: Options:

    -a  Edit the active script (any `script` given is ignored).

.. sievecmd:: xargs command [arg ...]

    Call `command` with the given arguments and each line from
    standard input as additional argument up to, but excluding,
    the first empty line or the end-of-file mark. The lines
    read from standard input are neither subject to
    `WORD SPLITTING`_ nor to `PATTERN EXPANSION`_. 


WORD SPLITTING
==============

Lines are split into words at any run of consecutive whitespace.
If a filename contains whitespace, that whitespace must either be
escaped with a backslash ("\\") or the filename must be quoted.

For example,

.. code:: none

    sieve://user@imap.foo.example> get foo bar

downloads the two files :file:`foo` and :file:`bar`.

But

.. code:: none

    sieve://user@imap.foo.example> get "foo bar"

downloads the single file :file:`foo bar`.

If a filename contains backslashes or quotes (single or double),
they must be escaped. For example,

.. code:: none

    sieve://user@imap.foo.example> get foo\\bar\'

downloads the file :file:`foo\\bar'`.

See :manpage:`sh(1)` and :manpage:`wordexp(3)` for details.


PATTERN EXPANSION
=================

If :samp:`*`, :samp:`?`, or :samp:`[{chars}]` occur in an expression given
with :option:`-e`, read from a script given with :option:`-s`, or read from
standard input, they are expanded to local or remote filenames in the same
way as a they would be expanded by a system shell. If a command operates on
local scripts, patterns are expanded to matching filenames on the local
system; if a command operates on remote scripts, patterns are expanded to
matching filenames on the remote system.

For example,

.. code:: none

    sieve://user@imap.foo.example> put *.sieve

uploads every *local* file that matches :samp:`*.sieve`.

But

.. code:: none

    sieve://user@imap.foo.example> get *.sieve

downloads every *remote* file that matches :samp:`*.sieve`.

If a filename contains :samp:`*`, :samp:`?`, or :samp:`[{chars}]`, those
characters must be escaped with a backslash ("\\") or the filename as a
whole must be quoted. For example,

.. code:: none

    sieve://user@imap.foo.example> get *.sieve

downloads every file that matches the *pattern* :samp:`*.sieve`.

But

.. code:: none

    sieve://user@imap.foo.example> get "*.sieve"

downloads the *file* :file:`*.sieve`.

See :manpage:`sh(1)` and :manpage:`fnmatch(3)` for details.


SCRIPTING
=========

Operations can be scripted by giving a :option:`command`,
redirecting standard input, or with :option:`-e` or :option:`-s`.


Basics
------

Scripts abort if an error occurs, so errors must be prevented.

Confirmation is always prompted for on the controlling terminal,
regardless of input/output redirection. If there is no controlling
terminal, operations that require confirmation trigger an error.

Comments start with a '#' and are ignored.


Comparing Scripts
-----------------

:sievecmd:`cmp` can be used to compare remote scripts.

.. code:: bash

    $ if sievemgr user@imap.foo.example cmp -s foo.sieve bar.sieve
    > then echo 'foo.sieve and bar.sieve are equal'
    > else echo 'foo.sieve and bar.sieve differ'
    > fi

.. code:: bash

    $ case $(sievemgr user@imap.foo.example cmp foo.sieve bar.sieve) in
    > (*equal)   echo 'foo.sieve and bar.sieve are equal' ;;
    > (*differs) echo 'foo.sieve and bar.sieve differ' ;;
    > esac


Listing Scripts
---------------

:sievecmd:`ls` prints one script name per line if standard input
is not a terminal. Sieve script names must not contain linefeeds,
so :sievecmd:`ls` can safely be used in scripts.

.. code:: bash

    $ sievemgr -e'ls -a' -e'deactivate' user@imap.foo.example |
    > sievemgr user@imap.foo.example xargs rm -f

.. code:: bash

    $ mkfifo pipe
    $ sievemgr user@imap.foo.example ls -l >pipe & pid=$!
    $ nscripts=0
    $ while read -r _ script && [ "$script" ]
    > do
    >     eval "script_${nscripts}"='$script'
    >     nscripts=$((nscripts + 1))
    > done <pipe
    $ wait $pid


Persistent Connections
----------------------

A connection is opened each time :command:`sievemgr` is called. So
if multiple messages are going to be exchanged between the client and
the server, it is more efficient to run :command:`sievemgr` in the
background and send and receive messages through pipes than to
call :command:`sievemgr` for each exchange.

Create one pipe for sending commands and another one for receiving responses:

.. code:: bash

    $ mkfifo -m 0600 send recv

Start :command:`sievemgr` and redirect its input and output to these pipes:

.. code:: bash

    $ sievemgr user@imap.foo.example <send >recv & pid=$!

Open the pipes in the shell:

.. code:: bash

    $ exec 3>send
    $ exec 4<recv

Commands can now be sent by writing them to file descriptor (FD) 3:

.. code:: bash

    $ echo ls -l >&3

And responses can be read from FD 4:

.. code-block:: bash
    :emphasize-lines: 6

    $ nscripts=0
    $ while read -r _ script && [ "$script" ]
    > do
    >     eval "script_${nscripts}"='$script'
    >     nscripts=$((nscripts + 1))
    > done <&4

However, be careful to avoid deadlocks:

.. code-block:: bash
    :caption: Deadlock
    :emphasize-lines: 3

    echo ls -a >&3
    # read will wait forever if there is no active script.
    active="$(read <&4)"

.. code-block:: bash
    :caption: Deadlock
    :emphasize-lines: 4

    echo ls -l >&3
    nscripts=0
    # The loop reads past the output of ls -l and then waits forever.
    while read -r _ script
    do
        eval "script_${nscripts}"='$script'
        nscripts=$((nscripts + 1))
    done <&4

The output of SieveManager commands must be checked
for an end-of-transmission mark:

======================  ==================================
Command                 End-of-transmission mark
======================  ==================================
:sievecmd:`caps`        ``...``
:sievecmd:`cert`        ``...``
:sievecmd:`cmp`         Ends with ``equal`` or ``differs``
:sievecmd:`ls -l <ls>`  First word is ``e``
======================  ==================================

Also be careful to avoid races:

.. code-block:: bash
    :caption: Race condition
    :emphasize-lines: 3

    echo get foo.sieve >&3
    # patch will likely run BEFORE get has downloaded foo.sieve.
    patch foo.sieve <patchfile

Use :sievecmd:`echo` to wait for previously sent commands to finish:

.. code-block:: bash
    :emphasize-lines: 3, 5-7

    $ cat <<EOF >&3
    > get foo.sieve
    > echo fin
    > EOF
    $ while read -r line && [ "$line" != fin ]
    > do :
    > done <&4
    $ patch foo.sieve <patchfile

:command:`read` blocks, so this is *not* a busy wait.

Exit by sending :sievecmd:`exit`.

.. code:: bash

    $ echo exit >&3
    $ wait "$pid"

But abort by:

.. code:: bash

    $ kill "$pid"
    $ wait "$pid"

Exiting by writing :sievecmd:`exit` to FD 3, instead of by sending a
:literal:`TERM` with :command:`kill`, makes sure that :command:`sievemgr`
exits only after it has finished executing previously sent commands.
Conversely, aborting with :command:`kill` makes sure that
:command:`sievemgr` exits right away.

See :download:`examples/sievepatch <../examples/sievepatch>`
for an extended example.


LOGIN
=====

.. only:: man

    Logins can be automated by reading passwords from the
    standard output of a command, from :manpage:`sieve.cf(5)`,
    or from :file:`.netrc`, or by using TLS client authentication.

.. only:: not man

    Logins can be automated by reading passwords from the
    standard output of a command, from :doc:`sieve.cf <config>`,
    or from :file:`.netrc`, or by using TLS client authentication.

Files that contain passwords must be neither group- nor world-readable.

.. danger::

   Password should be stored in encrypted form only.
   Prefer using a password manager over :file:`sieve.cf` or :file:`.netrc`.


Password Managers
-----------------

Set :confvar:`getpassword` to a :samp:`{command}` to read the password
from the standard output of that *command*.

For example, add

.. code:: none

    getpassword pass $login@$host

.. only:: man

    to your :manpage:`sieve.cf(5)` to query pass_
    for the password for the current `host`.

.. only:: not man

    to your :doc:`sieve.cf <config>` to query pass_
    for the password for the current `host`.

:samp:`$host` and :samp:`$login` are expanded to the given `host`
and the login for that `host` respectively.

.. warning::
    Commands that can be run by :command:`sievemgr` can, at least,
    be run by any application that can run :command:`python`.


The :file:`sieve.cf` File
-------------------------

Set :confvar:`password` to a :samp:`{string}` to log
in using that :samp:`{string}` as your password.

For example, add

.. code:: none

    account imap.foo.example
        login user
        password pencil

.. only:: man

    to your :manpage:`sieve.cf(5)` to automatically
    log in as :samp:`user` with the password :samp:`pencil`
    on :samp:`imap.foo.example`.

.. only:: not man

    to your :doc:`sieve.cf <config>` to automatically
    log in as :samp:`user` with the password :samp:`pencil`
    on :samp:`imap.foo.example`.


The :file:`.netrc` File
-----------------------

The :file:`.netrc` file is a traditional facility to automate logins.

For example, add

.. code:: none

    machine imap.foo.example
        login user
        password pencil

to your :file:`.netrc` to automatically log in as :samp:`user` with
the password :samp:`pencil` on :samp:`imap.foo.example`.

See the GNU Inetutils manual (chap. `11.7 <netrc_>`_) for details.


TLS Client Authentication
-------------------------

There are two types of TLS client authentication. Sending a TLS client
certificate may be required for another form of authentication to be permitted
to begin with or the user may be authenticated *by* sending a certificate.

Both types require sending a TLS client certificate. Set :confvar:`cert`
to a :file:`{file}` that contains a TLS key and a TLS certificate
to decrypt messages with that key and send that certificate. For example:

.. code:: none

    account imap.foo.example
        login user
        cert cert.pem

In addition, set :confvar:`saslmechs` to ``external`` to authenticate *by*
sending that certificate:

.. code:: none

    account imap.foo.example
        login user
        cert cert.pem
        saslmechs external

.. only:: man

    See :manpage:`sieve.cf(5)` for details.

.. only:: not man

    See :doc:`sieve.cf <config>` for details.


EXIT STATUS
===========

0
    Success

1
    Failure

2
    Usage error


ENVIRONMENT
===========

.. envvar:: COLUMNS

    Terminal width in characters.

.. envvar:: EDITOR

    Editor called by :sievecmd:`ed` (default: :command:`ed`).

.. envvar:: HOME

    Home directory of the current user.

.. envvar:: LANG, LC_ALL, LC_CTYPE

    Encoding for reading from/writing to the terminal and applications.
    Sieve scripts are always assumed to be encoded as UTF-8. The order of
    preference is :envvar:`LC_ALL` > :envvar:`LC_CTYPE` > :envvar:`LANG`.

.. envvar:: LINES

    Terminal height in lines.

.. envvar:: LOGNAME

    Login name of the current user.

.. envvar:: PAGER

    Pager called by :sievecmd:`more` (default: :command:`more`).

.. envvar:: NETRC

    Filename of the :file:`.netrc` file
    (default: :file:`{$HOME}/.netrc`).

.. envvar:: VISUAL

    Editor called by :sievecmd:`vi` (default: :command:`vi`).

.. envvar:: XDG_CONFIG_HOME

    X Desktop Group base configuration directory
    (default: :file:`{$HOME}/.config`).


FILES
=====

.. index:: pair: /etc/sieve/config; file
.. index:: pair: /etc/sieve.cf; file
.. index:: pair: $XDG_CONFIG_HOME/sieve/config; file
.. index:: pair: $HOME/.sieve/config; file
.. index:: pair: $HOME/.sieve.cf; file

:file:`/etc/sieve/config`, :file:`/etc/sieve.cf`, :file:`{$XDG_CONFIG_HOME}/sieve/config`, :file:`{$HOME}/.sieve/config`, :file:`{$HOME}/.sieve.cf`
    Default configuration files. Not read when :option:`-c` is given.

    .. only:: man

        See :manpage:`sieve.cf(5)` for details.

    .. only:: not man

        See :doc:`sieve.cf <config>` for details.

.. index:: pair: .netrc; file

:file:`.netrc`
    Login information.


STANDARDS
=========

:rfc:`2195` (CRAM-MD5)

:rfc:`2244` (ACAP)

:rfc:`2782` (SRV records)

:rfc:`4013` (SASLprep)

:rfc:`4422` (SASL)

:rfc:`4616` (PLAIN)

:rfc:`5228` (Sieve) 

:rfc:`5802` (SCRAM)

:rfc:`5804` (ManageSieve)

:rfc:`5019` (Lightweight OCSP)

:rfc:`6960` (OCSP)

:rfc:`7677` (SCRAM-SHA-256 and SCRAM-SHA-256-PLUS)


SECURITY
========

Credentials are stored in memory so that they need not be entered again
in case of a referral. However, because page-locking is unfeasible in
Python, they may be swapped out to the disk.

SieveManager can query password managers to automate logins. However,
any command that can be run by :command:`sievemgr` can, at the very least,
also be run by any application that can run :command:`python`.


BUGS
====

:file:`.netrc` records without a ``password`` token wrongly trigger
a parse error in Python up to version 3.9.


EXAMPLES
========

Upload :file:`script.sieve` to :samp:`imap.foo.example` and activate it:

.. code:: none

    $ sievemgr user@imap.foo.example
    sieve://user@imap.foo.example> put script.sieve
    sieve://user@imap.foo.example> activate script.sieve
    sieve://user@imap.foo.example> exit

Edit the active script on :samp:`imap.foo.example`:

.. code:: bash

    $ sievemgr user@imap.foo.example vi -a

Reading commands from standard input:

.. code:: bash

    $ sievemgr user@imap.foo.example <<EOF
    > put script.sieve
    > activate script.sieve
    > EOF

Download all scripts from :samp:`imap.foo.example`:

.. code:: bash

    $ sievemgr -e'get *' user@imap.foo.example


.. only:: man

    SEE ALSO
    ========

    :manpage:`sieve.cf(5)`

